package com.github.facadefs.support;

import com.github.facadefs.FileSystem;
import com.github.facadefs.FileView;
import com.github.facadefs.util.Operation;
import com.github.facadefs.Operations;
import com.github.facadefs.Selector;
import com.github.facadefs.util.MonitorInputStream;
import com.github.facadefs.util.Throwables;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 基于 Session 的文件系统.
 *
 * @author vacoor
 * @since 1.2.0
 */
public abstract class SessionBasedFileSystem<S> implements FileSystem {

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean exists(final String path) throws IOException {
        return null != stat(path);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public FileView stat(final String path) throws IOException {
        final S session = connect();
        try {
            return stat(session, path);
        } finally {
            disconnect(session);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public FileView[] ls(final String path) throws IOException {
        final S session = connect();
        try {
            return ls(session, path);
        } finally {
            disconnect(session);
        }
    }

    @Override
    public void ls(final String path, final Selector selector) throws IOException {
        final S session = connect();
        try {
            ls(session, path, selector);
        } finally {
            disconnect(session);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean mkdirs(final String path) throws IOException {
        final S session = connect();
        try {
            return mkdirs(session, path);
        } finally {
            disconnect(session);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rm(final String path, final boolean recursive) throws IOException {
        final S session = this.connect();
        try {
            this.rm(session, path, recursive);
        } finally {
            disconnect(session);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rename(final String oldPath, final String newPath) throws IOException {
        final S session = this.connect();
        try {
            this.rename(session, oldPath, newPath);
        } finally {
            disconnect(session);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public InputStream open(final String path) throws IOException {
        final S session = connect();
        try {
            return new MonitorInputStream(open(session, path)) {
                @Override
                protected void onClosed() throws IOException {
                    disconnect(session);
                }
            };
        } catch (final Throwable ex) {
            disconnect(session);
            return Throwables.rethrowIOException(ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public OutputStream create(final String path, final boolean override) throws IOException {
        return create(path, null, override);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public OutputStream create(final String path, final String contentType, final boolean override) throws IOException {
        final S session = connect();
        try {
            return new DataOutputStream(write(session, path, contentType, override)) {
                @Override
                public void close() throws IOException {
                    try {
                        super.close();
                    } finally {
                        disconnect(session);
                    }
                }
            };
        } catch (final Throwable ex) {
            disconnect(session);
            return Throwables.rethrowIOException(ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <R> R execute(final Operation<Operations, R> operation) throws IOException {
        final S session = this.connect();
        final SessionOperations operations = new SessionOperations(session);
        try {
            return operation.execute(operations);
        } finally {
            this.disconnect(session);
        }
    }

    /**
     * 创建一个新的 session 对象.
     *
     * @return session 对象
     * @throws IOException 如果发生 IO 异常
     */
    protected abstract S connect() throws IOException;

    /**
     * 断开给定的 session 对象.
     *
     * @param session session 对象
     */
    protected abstract void disconnect(final S session);

    /**
     * 获取路径的文件状态
     *
     * @param session session 对象
     * @param path    文件系统路径
     * @return 返回文件状态, 如果文件不存在返回 null
     */
    protected abstract FileView stat(final S session, final String path) throws IOException;

    /**
     * 获取文件/路径下的文件信息.
     *
     * @param session session 对象
     * @param path    文件或目录路径
     * @return 文件或目录的状态, 如果目录下没有文件或文件不存在返回空数组
     * @throws IOException 如果发生 IO 异常
     */
    protected abstract FileView[] ls(final S session, final String path) throws IOException;

    /**
     * 获取文件/路径下的文件信息.
     *
     * @param session  session 对象
     * @param path     文件或目录路径
     * @param selector 处理器
     * @throws IOException 如果发生 IO 异常
     * @since 1.2.0
     */
    protected abstract void ls(final S session, final String path, final Selector selector) throws IOException;

    /**
     * 创建给定路径的目录, 如果目录已经创建成功或已经存在返回 true, 创建失败返回 false.
     * 如果已经存在但不是一个目录则抛出异常.
     *
     * @param session session 对象
     * @param path    目录路径
     * @throws IOException 如果发生 IO 异常
     */
    protected abstract boolean mkdirs(final S session, final String path) throws IOException;

    /**
     * 删除给定路径.
     *
     * @param session session 对象
     * @param path    文件或目录路径
     * @param recurse 如果文件夹中存在目录是否递归删除
     * @throws IOException 如果发生 IO 异常
     */
    protected abstract void rm(final S session, final String path, final boolean recurse) throws IOException;

    /**
     * 重命名路径.
     *
     * @param session session 对象
     * @param oldPath 源路径
     * @param newPath 目标路径
     * @throws IOException 如果文件系统异常
     */
    protected abstract void rename(final S session, final String oldPath, final String newPath) throws IOException;

    /**
     * 打开给定路径的文件, 如果给定路径不存在返回null, 如果不是文件将抛出异常.
     *
     * @param session session 对象
     * @param path    文件路径
     * @return 文件输入流或null
     * @throws IOException 如果文件系统异常
     */
    protected abstract InputStream open(final S session, final String path) throws IOException;

    /**
     * 获取一个创建文件的输出流并自动创建相关目录, 当 override = false, 如果文件已经存在则抛出异常.
     *
     * @param session  session 对象
     * @param path     要创建的文件路径
     * @param override 如果文件已经存在是否覆盖
     * @return 新建文件的输出流
     * @throws IOException 如果文件系统异常
     */
    protected abstract OutputStream write(final S session, final String path, final String contentType, final boolean override) throws IOException;


    protected class SessionOperations implements Operations {
        private final S session;

        protected SessionOperations(final S session) {
            this.session = session;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean exists(final String path) throws IOException {
            return null != this.stat(path);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public FileView stat(final String path) throws IOException {
            return SessionBasedFileSystem.this.stat(session, path);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public FileView[] ls(final String path) throws IOException {
            return SessionBasedFileSystem.this.ls(session, path);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void ls(final String path, final Selector selector) throws IOException {
            SessionBasedFileSystem.this.ls(session, path, selector);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean mkdirs(final String path) throws IOException {
            return SessionBasedFileSystem.this.mkdirs(session, path);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void rm(final String path, final boolean recursive) throws IOException {
            SessionBasedFileSystem.this.rm(session, path, recursive);
        }

        @Override
        public void rename(final String oldPath, final String newPath) throws IOException {
            SessionBasedFileSystem.this.rename(session, oldPath, newPath);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public InputStream open(final String path) throws IOException {
            return SessionBasedFileSystem.this.open(session, path);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public OutputStream create(final String path, final boolean override) throws IOException {
            return this.create(path, null, override);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public OutputStream create(final String path, final String contentType, final boolean override) throws IOException {
            return SessionBasedFileSystem.this.write(session, path, contentType, override);
        }
    }

}
